﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Tetris
{
    class Engine
    {
        private class FigureComparer : IComparer<Figure>//implement Comparer by rows descending then by cols ascending
        {
            public int Compare(Figure a, Figure b)
            {
                if (a.GetTopLeft().Row != b.GetTopLeft().Row)
                {
                    return b.GetTopLeft().Row.CompareTo(a.GetTopLeft().Row);
                }
                else
                {
                    return a.GetTopLeft().Col.CompareTo(b.GetTopLeft().Col);
                }
            }
        }

        private int level = 1;
        private int linesForNextLevel = 2;
        private int linesCount = 0;
        private int score = 0;
        private bool IsGameOver = false;

        private DateTime start = DateTime.Now;

        private int worldRows;
        private int worldCols;

        private Random generator;
        private Figure nextFigure;
        private List<Figure> allFigures;
        private SortedSet<Figure> staticFigures;
        private Figure movingFigure;
        private decimal speed;//in milisecond;
        private IRenderer renderer;
        private IUserInterface userInterface;

        public Engine(IRenderer renderer, IUserInterface userInterface, int worldRows, int worldCols, decimal speed)
        {
            this.renderer = renderer;
            this.userInterface = userInterface;
            this.speed = speed;
            this.allFigures = new List<Figure>();
            this.staticFigures = new SortedSet<Figure>(new FigureComparer());
            this.worldRows = worldRows;
            this.worldCols = worldCols;
            generator = new Random();
            this.nextFigure = this.ProduceNextFigure((Tetrominoes)generator.Next(Enum.GetNames(typeof(Tetrominoes)).Length - 1));
            this.movingFigure = null;
        }

        public void GameOver()
        {
            this.IsGameOver = true;
        }

        public void AddMovableFigure(Figure movableFigure)
        {
            this.allFigures.Add(movableFigure);
            this.movingFigure = movableFigure;
        }

        public void AddStaticFigure(Figure staticFigure)
        {
            this.allFigures.Add(staticFigure);
            this.staticFigures.Add(staticFigure);
        }

        public void UpdateSpeed()//implement difficulty increase depending on linesCount
        {
            if (linesCount >= linesForNextLevel)
            {
                ++level;
                linesForNextLevel <<= 1;
                this.speed *= 1.0m / (1.0m + (decimal)TetrisMain.SpeedIncreaseFactorInPercent / 100);
            }
        }

        public void Run()
        {

            while (!this.IsGameOver)
            {
                Thread.Sleep((int)speed);

                if (this.movingFigure == null)
                {
                    this.movingFigure = this.nextFigure;
                    this.AddMovableFigure(movingFigure);
                    this.nextFigure = this.ProduceNextFigure((Tetrominoes)generator.Next(Enum.GetNames(typeof(Tetrominoes)).Length - 1));//-1 for excluding Tetrominoes.Block

                    if (CollisionDispatcher.HandleCollisions(movingFigure, this.staticFigures, Direction.Down, worldRows, worldCols))
                    {
                        break;
                    }
                }

                this.userInterface.ProcessInput();

                this.UpdateSpeed();

                this.UpdateLevel();

                this.UpdateRows();

                this.UpdateAllFigures();

                CollisionDispatcher.HandleCollisions(movingFigure, this.staticFigures, Direction.Down, worldRows, worldCols);

                this.AddProducedFigures();

                Console.Clear();

                this.renderer.Initialize();

                this.ShowUpdatedTimer();

                this.ShowLevel();

                this.ShowScore();

                this.ShowNextFigure();

                this.AddFiguresToRenderer();

                this.renderer.RenderAll();

                this.renderer.ClearBuffer();

                this.RemoveDestroyed();
            }
            
            ShowGameOver();
        }

        public void MoveLeft()
        {
            if (movingFigure.GetTopLeft().Col > 0)
            {
                movingFigure.Move(Direction.Left);
                CollisionDispatcher.HandleCollisions(movingFigure, this.staticFigures, Direction.Left, worldRows, worldCols);
            }
        }

        public void MoveRight()
        {
            if (movingFigure.GetTopRight().Col < this.worldCols - 1)
            {
                movingFigure.Move(Direction.Right);
                CollisionDispatcher.HandleCollisions(movingFigure, this.staticFigures, Direction.Right, worldRows, worldCols);
            }
        }

        public void Rotate()
        {
            movingFigure.Rotate();
            while (movingFigure.GetTopRight().Col >= this.worldCols)
            {
                this.MoveLeft();
            }
        }

        public void Drop()
        {
            while (!CollisionDispatcher.HandleCollisions(movingFigure, this.staticFigures, Direction.Down, worldRows, worldCols))
            {
                this.movingFigure.Move(Direction.Down);
            }
        }

        private void UpdateAllFigures()
        {
            foreach (Figure figure in this.allFigures)
            {
                figure.Update();
            }
        }

        private void ShowLevel()
        {
            Console.ResetColor();
            Console.SetCursorPosition(15, 5);
            Console.Write("Level: {0}", this.level);
        }

        private void ShowUpdatedTimer()
        {
            Console.ResetColor();
            Console.SetCursorPosition(15, 10);
            TimeSpan timeSpan = DateTime.Now - start;
            Console.Write("{0: 00}:{1: 00}", timeSpan.Minutes, timeSpan.Seconds);
        }

        private void ShowScore()
        {
            Console.ResetColor();
            Console.SetCursorPosition(15, 15);
            Console.Write("Score: {0}", this.score);
        }

        private void ShowGameOver()
        {
            Console.ResetColor();
            Console.SetCursorPosition(15, 20);
            Console.Write("Game Over!");
            Console.SetCursorPosition(0, 22);
        }

        private void ShowNextFigure()
        {
            char[,] image = this.nextFigure.GetImage();

            int rows = image.GetLength(0);
            int cols = image.GetLength(1);

            MatrixCoords topLeft = new MatrixCoords(0, 22);

            Console.SetCursorPosition(15, 1);
            Console.Write("Next: ");

            for (int r = 0; r < rows; r++)
            {
                for (int c = 0; c < cols; c++)
                {
                    if (image[r, c] != ' ')
                    {
                        Console.ForegroundColor = this.nextFigure.GetColor();
                        Console.SetCursorPosition(topLeft.Col + c, topLeft.Row + r);
                        Console.WriteLine(image[r, c]);
                    }
                }
            }
        }

        private void AddFiguresToRenderer()
        {
            foreach (Figure figure in this.allFigures)
            {
                this.renderer.EnqueueForRendering(figure);
            }
        }

        private Figure ProduceNextFigure(Tetrominoes figureType)
        {
            MatrixCoords topLeft = new MatrixCoords(0, TetrisMain.WorldCols / 2);

            switch (figureType)
            {
                case Tetrominoes.LineShape:
                        return new LineShapeFigure(topLeft);
                case Tetrominoes.LShape:
                        return new LShapeFigure(topLeft);
                case Tetrominoes.MirroredLShape:
                        return new MirroredLShapeFigure(topLeft);
                case Tetrominoes.SquareShape:
                        return new SquareShapeFigure(topLeft);
                case Tetrominoes.SShape:
                        return new SShapeFigure(topLeft);
                case Tetrominoes.TShape:
                        return new TShapeFigure(topLeft);
                case Tetrominoes.ZShape:
                        return new ZShapeFigure(topLeft);
                default:
                        return new IndestuctableTempraryBlock(topLeft, 100, true);
            }
        }

        private void UpdateRows()
        {
            List<int> destroyedLinesIndexes = new List<int>();

            int currentLineIndex = this.worldRows - 1;
            int countOfFoundOnCurrentLine = 0;
            foreach (Block block in staticFigures)
            {
                if (block.GetTopLeft().Row == currentLineIndex && block.IsDestructable)
                {
                    ++countOfFoundOnCurrentLine;
                }
                else
                {
                    if (countOfFoundOnCurrentLine == this.worldCols)
                    {
                        destroyedLinesIndexes.Add(currentLineIndex);
                    }
                    currentLineIndex = block.GetTopLeft().Row;
                    countOfFoundOnCurrentLine = 1;
                }
            }

            if (countOfFoundOnCurrentLine == this.worldCols)
            {
                destroyedLinesIndexes.Add(currentLineIndex);
            }

            if (destroyedLinesIndexes.Count > 0)
            {
                this.RemoveLines(destroyedLinesIndexes);
                this.UpdateScore(destroyedLinesIndexes.Count);
            }
        }

        private void UpdateScore(int linesDestroyed)
        {
            score += 1 << (linesDestroyed - 1);
            linesCount += linesDestroyed;
        }

        private void UpdateLevel()
        {
            if (this.linesCount > this.linesForNextLevel)
            {
                this.linesForNextLevel <<= 1;
                ++this.level;
            }
        }

        private void RemoveLines(List<int> destroyedLinesIndexes)
        {
            int length = destroyedLinesIndexes.Count;

            for (int i = 0; i < length; i++)
            {
                this.staticFigures.RemoveWhere(x => x.GetTopLeft().Row == destroyedLinesIndexes[i] + i);
                this.allFigures.RemoveAll(x => x.GetTopLeft().Row == destroyedLinesIndexes[i] + i);

                foreach (Block block in this.staticFigures)
                {
                    if (block.GetTopLeft().Row < destroyedLinesIndexes[i] + i)
                    {
                        block.Move(Direction.Down);
                    }
                }
            }
        }

        private void RemoveDestroyed()
        {
            this.allFigures.RemoveAll(x => x.IsDestroyed);
            if (this.movingFigure.IsDestroyed)
            {
                movingFigure = null;
            }
            this.staticFigures.RemoveWhere(x => x.IsDestroyed);
        }

        private void AddProducedFigures()
        {
            List<Figure> newFigures = new List<Figure>();
            foreach (Figure figure in this.allFigures)
            {
                newFigures.AddRange(figure.ProduceFigures());
            }

            this.allFigures.AddRange(newFigures);
            foreach (Figure figure in newFigures)
            {
                this.staticFigures.Add(figure);
            }
        }
    }
}
